package com.maodou.android.utils

import android.content.Context
import android.os.Environment
import android.util.Base64
import com.maodou.core.utils.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.GZIPInputStream
import java.util.zip.ZipInputStream

/**
 * @Author Xing
 * @CreateTime 2025年10月27日
 * @Description 文件处理工具类
 */
object FileUtils {

    /**
     * 将Base64编码的GZIP数据解码并保存
     * 
     * @param base64Content Base64编码的GZIP数据
     * @param uid 账号ID，用于命名文件
     * @param targetDir 目标目录
     * @return 保存后的文件
     * @throws IOException 如果解码或保存失败
     */
    @Throws(IOException::class)
    fun decodeBase64GzipToFile(base64Content: String, uid: String, targetDir: File): File {
        // 确保目标目录存在
        if (!targetDir.exists()) {
            targetDir.mkdirs()
        }

        val targetFile = File(targetDir, "$uid.tar.gz")

        if (targetFile.exists() && targetFile.length() > 0) {
            Logger.d("文件已存在: ${targetFile.absolutePath}, 大小: ${targetFile.length()}")
            return targetFile
        }

        try {
            val compressedData = Base64.decode(base64Content, Base64.DEFAULT)
            Logger.d("Base64解码后数据大小: ${compressedData.size} bytes")
            
            ByteArrayInputStream(compressedData).use { byteInput ->
                GZIPInputStream(byteInput).use { gzipInput ->
                    FileOutputStream(targetFile).use { fileOutput ->
                        val buffer = ByteArray(8 * 1024)
                        var bytesRead: Int
                        var totalBytes = 0
                        while (gzipInput.read(buffer).also { bytesRead = it } != -1) {
                            fileOutput.write(buffer, 0, bytesRead)
                            totalBytes += bytesRead
                        }
                        fileOutput.flush()
                        Logger.d("GZIP解压后数据大小: $totalBytes bytes")
                    }
                }
            }
            
            // 检测文件类型
            val fileType = detectFileType(targetFile)
            Logger.d("检测到的文件类型: $fileType")
            
        } catch (e: Exception) {
            if (targetFile.exists()) {
                targetFile.delete()
            }
            throw IOException("Failed to decode and decompress file: ${e.message}", e)
        }

        return targetFile
    }
    
    /**
     * 检测文件类型
     */
    private fun detectFileType(file: File): String {
        if (!file.exists() || file.length() < 4) {
            return "UNKNOWN"
        }
        
        val header = ByteArray(4)
        FileInputStream(file).use { it.read(header) }
        
        return when {
            // ZIP: 50 4B 03 04
            header[0] == 0x50.toByte() && header[1] == 0x4B.toByte() && 
            header[2] == 0x03.toByte() && header[3] == 0x04.toByte() -> "ZIP"
            // GZIP: 1F 8B
            header[0] == 0x1F.toByte() && header[1] == 0x8B.toByte() -> "GZIP"
            // TAR (ustar): 检查偏移257-261位置
            else -> {
                if (file.length() > 262) {
                    val tarHeader = ByteArray(262)
                    FileInputStream(file).use { it.read(tarHeader) }
                    val ustar = String(tarHeader, 257, 5, Charsets.US_ASCII)
                    if (ustar == "ustar") "TAR" else "UNKNOWN (${header.joinToString(" ") { "%02X".format(it) }})"
                } else {
                    "UNKNOWN (${header.joinToString(" ") { "%02X".format(it) }})"
                }
            }
        }
    }

    /**
     * 解压文件到指定目录（自动检测格式）
     */
    @Throws(IOException::class)
    private fun extractToDirectory(archiveFile: File, destDir: File) {
        if (!archiveFile.exists()) {
            throw IOException("压缩文件不存在: ${archiveFile.absolutePath}")
        }
        
        if (archiveFile.length() == 0L) {
            throw IOException("压缩文件为空: ${archiveFile.absolutePath}")
        }
        
        if (!destDir.exists()) {
            destDir.mkdirs()
        }
        
        val fileType = detectFileType(archiveFile)
        Logger.d("开始解压文件: ${archiveFile.absolutePath}, 类型: $fileType, 大小: ${archiveFile.length()} bytes")
        
        when (fileType) {
            "ZIP" -> unzipToDirectory(archiveFile, destDir)
            "GZIP" -> {
                val tmpTar = File(destDir, "${archiveFile.nameWithoutExtension}.tar")
                gunzipToFile(archiveFile, tmpTar)
                try {
                    untarToDirectory(tmpTar, destDir)
                } finally {
                    if (tmpTar.exists()) tmpTar.delete()
                }
            }
            "TAR" -> untarToDirectory(archiveFile, destDir)
            else -> {
                val n = archiveFile.name.lowercase()
                if (n.endsWith(".tar")) {
                    untarToDirectory(archiveFile, destDir)
                } else if (n.endsWith(".tar.gz") || n.endsWith(".tgz")) {
                    val tmpTar = File(destDir, "${archiveFile.nameWithoutExtension}.tar")
                    gunzipToFile(archiveFile, tmpTar)
                    try {
                        untarToDirectory(tmpTar, destDir)
                    } finally {
                        if (tmpTar.exists()) tmpTar.delete()
                    }
                } else {
                    throw IOException("不支持的文件格式: $fileType")
                }
            }
        }
    }
    
    /**
     * 解压 ZIP 到指定目录
     */
    @Throws(IOException::class)
    private fun unzipToDirectory(zipFile: File, destDir: File) {
        if (!destDir.exists()) {
            destDir.mkdirs()
        }
        var fileCount = 0
        var totalSize = 0L
        
        try {
            ZipInputStream(BufferedInputStream(zipFile.inputStream())).use { zis ->
                val destCanonical = destDir.canonicalPath
                var entry = zis.nextEntry
                val buffer = ByteArray(8 * 1024)
                
                while (entry != null) {
                    val outFile = File(destDir, entry.name)
                    val outCanonical = outFile.canonicalPath

                    if (!outCanonical.startsWith(destCanonical)) {
                        Logger.w("跳过非法路径: ${entry.name}")
                        zis.closeEntry()
                        entry = zis.nextEntry
                        continue
                    }
                    
                    if (entry.isDirectory) {
                        Logger.d("创建目录: ${entry.name}")
                        outFile.mkdirs()
                    } else {
                        Logger.d("解压文件: ${entry.name}")
                        outFile.parentFile?.mkdirs()
                        BufferedOutputStream(FileOutputStream(outFile)).use { bos ->
                            var bytesRead = 0
                            while (true) {
                                val read = zis.read(buffer)
                                if (read == -1) break
                                bos.write(buffer, 0, read)
                                bytesRead += read
                            }
                            bos.flush()
                            totalSize += bytesRead
                            Logger.d("  -> 写入 $bytesRead bytes")
                        }
                        fileCount++
                    }
                    zis.closeEntry()
                    entry = zis.nextEntry
                }
            }
            Logger.d("ZIP解压完成: 共 $fileCount 个文件, 总大小 $totalSize bytes")
        } catch (e: Exception) {
            Logger.e("ZIP解压失败: ${e.message}", e)
            throw IOException("ZIP解压失败: ${e.message}", e)
        }
    }

    @Throws(IOException::class)
    private fun gunzipToFile(gzipFile: File, outFile: File) {
        GZIPInputStream(BufferedInputStream(FileInputStream(gzipFile))).use { gis ->
            FileOutputStream(outFile).use { fos ->
                val buffer = ByteArray(8 * 1024)
                while (true) {
                    val read = gis.read(buffer)
                    if (read == -1) break
                    fos.write(buffer, 0, read)
                }
                fos.flush()
            }
        }
    }

    @Throws(IOException::class)
    private fun untarToDirectory(tarFile: File, destDir: File) {
        if (!destDir.exists()) {
            destDir.mkdirs()
        }
        val destCanonical = destDir.canonicalPath
        BufferedInputStream(FileInputStream(tarFile)).use { input ->
            val header = ByteArray(512)
            val buffer = ByteArray(8 * 1024)
            var fileCount = 0
            var totalSize = 0L
            while (true) {
                var readHeader = 0
                while (readHeader < 512) {
                    val r = input.read(header, readHeader, 512 - readHeader)
                    if (r == -1) return
                    readHeader += r
                }
                var empty = true
                for (b in header) {
                    if (b.toInt() != 0) { empty = false; break }
                }
                if (empty) {
                    Logger.d("TAR解压完成: 共 $fileCount 个文件, 总大小 $totalSize bytes")
                    return
                }

                fun readNullTerminated(bytes: ByteArray, start: Int, len: Int): String {
                    var end = start
                    val max = start + len
                    while (end < max && bytes[end] != 0.toByte()) end++
                    return String(bytes, start, end - start, Charsets.US_ASCII)
                }
                val namePart = readNullTerminated(header, 0, 100)
                val prefixPart = readNullTerminated(header, 345, 155)
                val fullName = if (prefixPart.isNotEmpty()) "$prefixPart/$namePart" else namePart

                val rawSize = String(header, 124, 12, Charsets.US_ASCII)
                val size = rawSize.trim { it <= ' ' || it == '\u0000' }.let { s ->
                    var acc = 0L
                    for (ch in s) {
                        val d = (ch.code - '0'.code)
                        if (d !in 0..7) break
                        acc = (acc shl 3) + d
                    }
                    acc
                }
                val typeFlag = header[156]

                val outFile = File(destDir, fullName)
                val outCanonical = outFile.canonicalPath
                if (!outCanonical.startsWith(destCanonical)) {
                    var toSkip = ((size + 511) / 512) * 512
                    while (toSkip > 0) {
                        val s = input.read(buffer, 0, minOf(buffer.size.toLong(), toSkip).toInt())
                        if (s == -1) break
                        toSkip -= s
                    }
                    continue
                }

                when (typeFlag.toInt()) {
                    '5'.code -> {
                        outFile.mkdirs()
                    }
                    '0'.code, 0 -> {
                        outFile.parentFile?.mkdirs()
                        var remaining = size
                        BufferedOutputStream(FileOutputStream(outFile)).use { fos ->
                            while (remaining > 0) {
                                val r = input.read(buffer, 0, minOf(buffer.size.toLong(), remaining).toInt())
                                if (r == -1) throw IOException("读取TAR内容失败")
                                fos.write(buffer, 0, r)
                                remaining -= r
                                totalSize += r
                            }
                            fos.flush()
                        }
                        val padding = (512 - (size % 512)) % 512
                        var padLeft = padding
                        while (padLeft > 0) {
                            val r = input.read(buffer, 0, minOf(buffer.size.toLong(), padLeft).toInt())
                            if (r == -1) throw IOException("读取TAR填充失败")
                            padLeft -= r
                        }
                        fileCount++
                    }
                    'x'.code, 'g'.code, '2'.code, '3'.code, '4'.code, '1'.code -> {
                        var toSkip = ((size + 511) / 512) * 512
                        while (toSkip > 0) {
                            val r = input.read(buffer, 0, minOf(buffer.size.toLong(), toSkip).toInt())
                            if (r == -1) throw IOException("跳过TAR扩展头失败")
                            toSkip -= r
                        }
                    }
                    else -> {
                        var toSkip = ((size + 511) / 512) * 512
                        while (toSkip > 0) {
                            val r = input.read(buffer, 0, minOf(buffer.size.toLong(), toSkip).toInt())
                            if (r == -1) throw IOException("跳过未知TAR类型失败")
                            toSkip -= r
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取认证文件的解压路径
     */
    suspend fun getAuthFileFile(appContext: Context, fileID: String, zipFile: File): File? = withContext(Dispatchers.IO) {
        val dir = appContext.filesDir ?: appContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
        if (dir == null) {
            Logger.e("无法获取文件目录")
            return@withContext null
        }
        
        val unpackDir = File(dir, "tmp${File.separator}$fileID")

        val marker = File(dir, "tmp${File.separator}${fileID}.unzipped")
        if (unpackDir.exists() && marker.exists()) {
            return@withContext unpackDir
        }

        val tmpDir = File(dir, "tmp${File.separator}$fileID.tmp")
        try {
            if (tmpDir.exists()) {
                tmpDir.deleteRecursively()
            }
            tmpDir.mkdirs()

            // 解压到临时目录
            extractToDirectory(zipFile, tmpDir)

            File(dir, "tmp${File.separator}${fileID}.unzipped").writeText(System.currentTimeMillis().toString())

            val innerMarker = File(tmpDir, ".unzipped")
            if (innerMarker.exists()) innerMarker.delete()

            if (unpackDir.exists()) {
                unpackDir.deleteRecursively()
            }
            
            if (!tmpDir.renameTo(unpackDir)) {
                tmpDir.copyRecursively(unpackDir, overwrite = true)
                tmpDir.deleteRecursively()
                Logger.d("使用复制方式替换目录")
            } else {
                Logger.d("目录重命名成功")
            }
            
            val finalInnerMarker = File(unpackDir, ".unzipped")
            if (finalInnerMarker.exists()) finalInnerMarker.delete()

            return@withContext unpackDir
        } catch (e: Exception) {
            Logger.e("解压文件失败: ${e.message}", e)
            if (tmpDir.exists()) {
                tmpDir.deleteRecursively()
            }
            return@withContext null
        }
    }

}
